{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "11b1154a-5ebb-4cd9-8309-2f561787efd7",
   "metadata": {},
   "source": [
    "# 正则表达式\n",
    "\n",
    "标准库re，默认进行贪心匹配"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f94fc95-72b5-4155-9b5e-b897607ea84e",
   "metadata": {},
   "source": [
    "## 速查表\n",
    "\n",
    "### 通用规则\n",
    "\n",
    "绝大多数编程语言的正则表达式都有这些规则。\n",
    "\n",
    "\n",
    "| 符号           | 解释                             | 示例               | 说明                                                         |\n",
    "| -------------- | -------------------------------- | ------------------ | ------------------------------------------------------------ |\n",
    "| `.`            | 匹配任意字符                     | `b.t`              | 可以匹配bat / but / b#t / b1t等                              |\n",
    "| `\\w`           | 匹配字母/数字/下划线             | `b\\wt`             | 可以匹配bat / b1t / b_t等<br>但不能匹配b#t                   |\n",
    "| `\\s`           | 匹配空白字符（包括\\r、\\n、\\t等） | `love\\syou`        | 可以匹配love you                                             |\n",
    "| `\\d`           | 匹配数字                         | `\\d\\d`             | 可以匹配01 / 23 / 99等                                       |\n",
    "| `\\b`           | 匹配单词的边界                   | `\\bThe\\b`          |                                                              |\n",
    "| `^`            | 匹配字符串的开始                 | `^The`             | 可以匹配The开头的字符串                                      |\n",
    "| `$`            | 匹配字符串的结束                 | `.exe$`            | 可以匹配.exe结尾的字符串                                     |\n",
    "| `\\W`           | 匹配非字母/数字/下划线           | `b\\Wt`             | 可以匹配b#t / b@t等<br>但不能匹配but / b1t / b_t等           |\n",
    "| `\\S`           | 匹配非空白字符                   | `love\\Syou`        | 可以匹配love#you等<br>但不能匹配love you                     |\n",
    "| `\\D`           | 匹配非数字                       | `\\d\\D`             | 可以匹配9a / 3# / 0F等                                       |\n",
    "| `\\B`           | 匹配非单词边界                   | `\\Bio\\B`           |                                                              |\n",
    "| `[]`           | 匹配来自字符集的任意单一字符     | `[aeiou]`          | 可以匹配任一元音字母字符                                     |\n",
    "| `[^]`          | 匹配不在字符集中的任意单一字符   | `[^aeiou]`         | 可以匹配任一非元音字母字符                                   |\n",
    "| `*`            | 匹配0次或多次                    | `\\w*`              |                                                              |\n",
    "| `+`            | 匹配1次或多次                    | `\\w+`              |                                                              |\n",
    "| `?`            | 匹配0次或1次                     | `\\w?`              |                                                              |\n",
    "| `{N}`          | 匹配N次                          | `\\w{3}`            |                                                              |\n",
    "| `{M,}`         | 匹配至少M次                      | `\\w{3,}`           |                                                              |\n",
    "| `{M,N}`        | 匹配至少M次至多N次               | `\\w{3,6}`          |                                                              |\n",
    "| `\\|`            | 分支                             | `foo\\|bar`          | 可以匹配foo或者bar                                           |\n",
    "| `(?#)`         | 注释                             |                    |                                                              |\n",
    "| `(exp)`        | 匹配exp并捕获到自动命名的组中    |                    |                                                              |\n",
    "| `(?<name>exp)` | 匹配exp并捕获到名为name的组中    |                    |                                                              |\n",
    "| `(?:exp)`      | 匹配exp但是不捕获匹配的文本      |                    |                                                              |\n",
    "| `(?=exp)`      | 匹配exp前面的位置                | `\\b\\w+(?=ing)`     | 可以匹配I'm dancing中的danc                                  |\n",
    "| `(?<=exp)`     | 匹配exp后面的位置                | `(?<=\\bdanc)\\w+\\b` | 可以匹配I love dancing and reading中的第一个ing              |\n",
    "| `(?!exp)`      | 匹配后面不是exp的位置            |                    |                                                              |\n",
    "| `(?<!exp)`     | 匹配前面不是exp的位置            |                    |                                                              |\n",
    "| `*?`           | 重复任意次，但尽可能少重复       | `a.*b`<br>`a.*?b`  | 将正则表达式应用于aabab，前者会匹配整个字符串aabab，后者会匹配aab和ab两个字符串 |\n",
    "| `+?`           | 重复1次或多次，但尽可能少重复    |                    |                                                              |\n",
    "| `??`           | 重复0次或1次，但尽可能少重复     |                    |                                                              |\n",
    "| `{M,N}?`       | 重复M到N次，但尽可能少重复       |                    |                                                              |\n",
    "| `{M,}?`        | 重复M次以上，但尽可能少重复      |                    |                                                              |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5362d5c-2587-4dd4-b246-609ba2a24069",
   "metadata": {},
   "source": [
    "### re速查表\n",
    "\n",
    "Python 提供了`re`模块来支持正则表达式相关操作，下面是`re`模块中的核心函数。绝大多数可以使用面向对象写法。\n",
    "\n",
    "| 函数                                           | 说明                                                         |\n",
    "| ---------------------------------------------- | ------------------------------------------------------------ |\n",
    "| `compile(pattern, flags=0)`                    | 定义匹配模式，返回正则表达式对象 `Pattern`。用于面向对象写法                             |\n",
    "| `match(pattern, string, flags=0)`              | 用正则表达式匹配字符串 成功返回匹配对象`Match` 否则返回`None`       |\n",
    "| `search(pattern, string, flags=0)`             | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回`None` |\n",
    "| `split(pattern, string, maxsplit=0, flags=0)`  | 用正则表达式指定的模式分隔符拆分字符串 返回列表              |\n",
    "| `sub(pattern, repl, string, count=0, flags=0)` | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用`count`指定替换的次数 |\n",
    "| `fullmatch(pattern, string, flags=0)`          | `match`函数的完全匹配（从字符串开头到结尾）版本              |\n",
    "| `findall(pattern, string, flags=0)`            | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表        |\n",
    "| `finditer(pattern, string, flags=0)`           | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器          |\n",
    "| `purge()`                                      | 清除隐式编译的正则表达式的缓存                               |\n",
    "| `re.I` / `re.IGNORECASE`                       | 忽略大小写匹配标记                                           |\n",
    "| `re.M` / `re.MULTILINE`                        | 多行匹配标记                                                 |\n",
    "| `re.VERBOSE`                        | 忽略空白，使正则表达式可以使用注释支持注释                                                 |\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f822d2e5-7f53-463a-bded-2ff925f5ccd4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "# 不使用注释\n",
    "pattern = re.compile(r'((\\d{3}|\\(\\d{3}\\))?(\\s|-|\\.)?\\d{3}(\\s|-|\\.)\\d{4}(\\s*(ext|x|ext.)\\s*\\d{2,5})?)')# 这个正则表达式难以阅读,可以添加注释简化阅读\n",
    "# 三重引号表示多行字符串,也可表示注释\n",
    "pattern = re.compile(r'''(\n",
    "    (\\d{3}|\\(\\d{3}\\))? # 区号\n",
    "    (\\s|-|\\.)? # 分隔符\n",
    "    \\d{3} # 3个数字\n",
    "    (\\s|-|\\.) # 分隔符\n",
    "    \\d{4} # 4个数字\n",
    "    (\\s*(ext|x|ext.)\\s*\\d{2,5})? # 扩展\n",
    "    )''', re.VERBOSE)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f7904336-be6e-43c3-81b2-83709f67097f",
   "metadata": {},
   "source": [
    "## 捕获组与非捕获组\n",
    "\n",
    "正则表达式使用小括号 `()` 分组，在进行正则匹配时会自动忽视小括号。\n",
    "\n",
    "正则表达式中的捕获组和非捕获组是用于对匹配模式进行分组管理的核心机制：\n",
    "\n",
    "- 捕获组：不仅匹配指定模式，还会将匹配结果存储在内存中，支持通过编号 / 命名回溯引用（Backreferences），或在匹配后提取使用。\n",
    "- 非捕获组：仅用于分组匹配逻辑（如多模式分支、量词作用域），不存储匹配结果，节省内存且提升性能。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17d797d1-992b-4206-a932-8f87abadd40d",
   "metadata": {},
   "source": [
    "### 捕获组（Capturing Groups）\n",
    "\n",
    "- `(pattern)`\t基础捕获组：按出现顺序编号（从 1 开始），0 代表整个正则匹配结果。\n",
    "- `(?<name>pattern)`\t命名捕获组：给组命名，避免编号混淆。\n",
    "\n",
    "捕获组的核心价值：\n",
    "\n",
    "- 提取匹配的子串；\n",
    "- 替换时复用匹配结果\n",
    "- 正则内部回溯引用重复模式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "abff30e5-f137-43f5-b95c-a2b93778f307",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "整个匹配： freeCodeCamp\n",
      "第一个捕获组： Code\n",
      "paidcoooooodeworld\n",
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 不使用命名的捕获组\n",
    "\n",
    "# 1. 提取捕获组内容\n",
    "regex = r\"free(code)camp\"\n",
    "str1 = \"freeCodeCamp\"\n",
    "match_result = re.search(regex, str1, re.IGNORECASE)\n",
    "if match_result:\n",
    "    print(\"整个匹配：\", match_result.group(0))  # freeCodeCamp\n",
    "    print(\"第一个捕获组：\", match_result.group(1))  # Code\n",
    "\n",
    "# 2. 替换时复用捕获组（\\1 引用第一个捕获组）\n",
    "regex2 = r\"free(co+de)camp\"\n",
    "str2 = \"freecoooooodecamp\"\n",
    "replace_result = re.sub(regex2, r\"paid\\1world\", str2, flags=re.IGNORECASE)\n",
    "print(replace_result)  # paidcoooooodeworld\n",
    "\n",
    "# 3. 正则内部回溯引用（\\\\1 引用第一个捕获组）\n",
    "regex3 = r\"free(co+de)camp.*free\\1camp\"\n",
    "print(re.search(regex3, \"freecooooodecamp love freecooooodecamp\", re.IGNORECASE) is not None)  # True\n",
    "print(re.search(regex3, \"freecooooodecamp love freecodecamp\", re.IGNORECASE) is not None)  # False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e97f68e3-6c0e-4d45-912a-830b723d8769",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "cooooode\n",
      "paidcooooodeworld\n"
     ]
    }
   ],
   "source": [
    "# 命名捕获组\n",
    "\n",
    "# 1. 正则内命名回溯引用（(?P=code) 引用名为 code 的捕获组）\n",
    "regex = r\"free(?P<code>co+de)camp.*free(?P=code)camp\"\n",
    "print(re.search(regex, \"freecooooodecamp is freecooooodecamp\", re.IGNORECASE) is not None)  # True\n",
    "\n",
    "# 2. 提取命名捕获组内容\n",
    "match_result = re.search(r\"free(?P<code>co+de)camp\", \"freecooooodecamp\", re.IGNORECASE)\n",
    "print(match_result.group(\"code\"))  # cooooode\n",
    "\n",
    "# 3. 替换时引用命名捕获组（\\g<code>）\n",
    "replace_result = re.sub(\n",
    "    r\"free(?P<code>co+de)camp\",\n",
    "    r\"paid\\g<code>world\",\n",
    "    \"freecooooodecamp\",\n",
    "    flags=re.IGNORECASE\n",
    ")\n",
    "print(replace_result)  # paidcooooodeworld\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1c77e07-fb62-4000-89ee-66161ebc3d99",
   "metadata": {},
   "source": [
    "### 非捕获组\n",
    "\n",
    "语法：`(?:pattern)`\n",
    "\n",
    "核心特性：仅用于分组（如限定 | 的作用域、量词作用域），不存储匹配结果，不占用捕获组编号，节省内存且提升正则执行效率。\n",
    "\n",
    "适用场景：\n",
    "\n",
    "- 多模式分支（如 code|candy）需归为一组，且无需提取该分支内容；\n",
    "- 给一组字符加量词（如 (?:abc)+ 匹配多次 abc），且无需捕获分组内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "282deb4a-96c8-4c7a-a021-fa3dca20ccaf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "True\n",
      "()\n",
      "('code',)\n"
     ]
    }
   ],
   "source": [
    "# 非捕获组匹配 freecodecamp 或 freecandycamp\n",
    "regex = r\"free(?:code|candy)camp\"\n",
    "print(re.search(regex, \"freecodecamp\", re.IGNORECASE) is not None)  # True\n",
    "print(re.search(regex, \"freecandycamp\", re.IGNORECASE) is not None)  # True\n",
    "\n",
    "# 对比：非捕获组无分组结果\n",
    "match_result = re.search(regex, \"freecodecamp\", re.IGNORECASE)\n",
    "print(match_result.groups())  # () （空元组，无捕获内容）\n",
    "\n",
    "# 若用捕获组：(code|candy)\n",
    "regex_capture = r\"free(code|candy)camp\"\n",
    "match_result2 = re.search(regex_capture, \"freecodecamp\", re.IGNORECASE)\n",
    "print(match_result2.groups())  # ('code',) （有捕获内容）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7a822ae-89e4-47ff-8a67-7f80520a6f46",
   "metadata": {},
   "source": [
    "## 前瞻和后顾\n",
    "\n",
    "正则中的前瞻（Lookahead） 和后顾（Lookbehind） 断言（也叫 “零宽断言”）是一类 “条件匹配” 机制：它们只检查目标位置后面（前瞻） 或前面（后顾） 是否存在指定模式，本身不消耗字符、不纳入最终匹配结果，仅作为匹配的 “条件判断”。\n",
    "\n",
    "主要分为：正向前瞻、负向前瞻、正向后顾、负向后顾\n",
    "\n",
    "前瞻断言在后，后顾断言在前。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c722b1e-4c14-42e9-b1fd-1ccc6c79784f",
   "metadata": {},
   "source": [
    "### 正向前瞻（Positive Lookahead）\n",
    "\n",
    "语法：`(?=pattern)`\n",
    "\n",
    "含义：当前位置右侧必须匹配pattern，才会继续匹配整体正则表达式。\n",
    "\n",
    "特点：仅判断存在性，pattern本身不被纳入最终匹配结果，也不消耗字符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "489d5833-7a68-4e19-bd4e-f421cd8a3da4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('free', None, None)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 正则表达式：匹配free，且free后面紧跟code（不区分大小写）\n",
    "# 只匹配freecode中的code，free后面必须要有code\n",
    "regex = re.compile(r'free(?=code)', re.IGNORECASE)\n",
    "\n",
    "# 测试字符串\n",
    "s1 = \"freeCodeCamp\"\n",
    "s2 = \"free code camp\"\n",
    "s3 = \"I need someone for free to write code for me\"\n",
    "\n",
    "# 查找匹配项\n",
    "m1 = regex.search(s1)\n",
    "m2 = regex.search(s2)\n",
    "m3 = regex.search(s3)\n",
    "\n",
    "# 只有字符串s1匹配到了内容\n",
    "m1.group(), m2, m3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74d406de-0088-4d07-b040-f7a985b4a31c",
   "metadata": {},
   "source": [
    "### 负向前瞻（Negative Lookahead）\n",
    "\n",
    "语法：`(?!pattern)`\n",
    "\n",
    "含义：当前位置右侧必须不匹配pattern，才会继续匹配整体正则表达式。和正向前瞻相反。\n",
    "\n",
    "特点：与正向前瞻相反，判断 “不存在” 指定模式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a7793683-9a9f-428a-8c01-325ff276b859",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, 'free', 'free')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 正则表达式：匹配free，且free后面不紧跟code（不区分大小写）\n",
    "# 不匹配freecode中的free\n",
    "regex = re.compile(r'free(?!code)', re.IGNORECASE)\n",
    "\n",
    "# 测试字符串\n",
    "s1 = \"freeCodeCamp\"\n",
    "s2 = \"free code camp\"\n",
    "s3 = \"I need someone for free to write code for me\"\n",
    "\n",
    "# 查找匹配项\n",
    "m1 = regex.search(s1)\n",
    "m2 = regex.search(s2)\n",
    "m3 = regex.search(s3)\n",
    "\n",
    "# 只有s1没有匹配到内容\n",
    "m1, m2.group(), m3.group()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbcfc0c3-daba-4014-91db-28120f0ca486",
   "metadata": {},
   "source": [
    "### 正向后顾（Positive Lookbehind）\n",
    "\n",
    "语法：`(?<=pattern)`\n",
    "\n",
    "含义：当前位置左侧必须匹配pattern，才会继续匹配整体正则表达式。\n",
    "\n",
    "特点：pattern的长度通常为固定值（Python 3.5 + 支持可变长度），且不被纳入最终匹配结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "39fd4273-17f4-46da-a072-4daecf4b7692",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Code', None, None)"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 正则表达式：匹配code，且code前面紧跟free（不区分大小写）\n",
    "# 只匹配freecode中的code\n",
    "regex = re.compile(r'(?<=free)code', re.IGNORECASE)\n",
    "\n",
    "# 测试字符串\n",
    "s1 = \"freeCodeCamp\"\n",
    "s2 = \"free code camp\"\n",
    "s3 = \"I need someone for free to write code for me\"\n",
    "\n",
    "# 查找匹配项\n",
    "m1 = regex.search(s1)\n",
    "m2 = regex.search(s2)\n",
    "m3 = regex.search(s3)\n",
    "\n",
    "m1.group(), m2, m3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5856a2cf-89eb-455d-8399-57686d7fddfd",
   "metadata": {},
   "source": [
    "### 负向后顾（Negative Lookbehind）\n",
    "\n",
    "语法：`(?<!pattern)`\n",
    "\n",
    "含义：当前位置左侧必须不匹配pattern，才会继续匹配整体正则表达式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f967a952-430a-4ccf-b767-c603423abd57",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(None, 'code', 'code')"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "# 正则表达式：匹配code，且code前面不紧跟free（不区分大小写）\n",
    "# 不匹配freecode中的code\n",
    "regex = re.compile(r'(?<!free)code', re.IGNORECASE)\n",
    "\n",
    "# 测试字符串\n",
    "s1 = \"freeCodeCamp\"\n",
    "s2 = \"free code camp\"\n",
    "s3 = \"I need someone for free to write code for me\"\n",
    "\n",
    "# 查找匹配项\n",
    "m1 = regex.search(s1)\n",
    "m2 = regex.search(s2)\n",
    "m3 = regex.search(s3)\n",
    "\n",
    "m1, m2.group(), m3.group()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b077b1f2-2f1f-4326-8f7e-548c3b831415",
   "metadata": {},
   "source": [
    "### 应用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "20765f7e-498b-44da-aa53-d47d0a8a424d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "# 密码强度验证：要求密码包含至少一个大写字母、一个小写字母、一个数字，且长度 8-16 位。\n",
    "import re\n",
    "\n",
    "def check_password(password):\n",
    "    # 正向前瞻判断是否包含大写、小写、数字，且长度8-16\n",
    "    regex = re.compile(r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\\d).{8,16}$')\n",
    "    return bool(regex.match(password))\n",
    "\n",
    "print(check_password(\"Abc12345\"))  # True（满足所有条件）\n",
    "print(check_password(\"abc12345\"))   # False（无大写字母）\n",
    "print(check_password(\"Abc123\"))     # False（长度不足）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "8f334f2f-578a-42f9-9a91-acb3d3e5386a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['99', '19.9', '25.5']\n"
     ]
    }
   ],
   "source": [
    "# 提取价格中的数字：匹配 “¥” 后面的数字（正向后顾）\n",
    "import re\n",
    "\n",
    "s = \"苹果¥99，香蕉¥19.9，橙子¥25.5\"\n",
    "regex = re.compile(r'(?<=¥)\\d+\\.?\\d*')\n",
    "prices = regex.findall(s)\n",
    "print(prices)  # ['99', '19.9', '25.5']（仅匹配¥后面的数字）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7948f45a-ddef-47d9-a767-a607cb8b814e",
   "metadata": {},
   "source": [
    "## re\n",
    "\n",
    "re同时提供函数写法和面向对象写法，简单任务使用函数写法比较简洁。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "271d49d9-f425-4995-aef8-f1b3ee1f49ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "import re"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43caf7e2-0e2e-4b03-897e-f1a8fc8c3874",
   "metadata": {},
   "source": [
    "### 面向对象写法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "db3b7e6a-a43d-492c-9def-29068dbe5008",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ma\n"
     ]
    }
   ],
   "source": [
    "my_str = 'Math is intresting, and I like learing mathmatics.'\n",
    "\n",
    "# 创建Pattern\n",
    "pattern = re.compile('ma')\n",
    "\n",
    "# Match对象\n",
    "match = pattern.search(my_str)\n",
    "# print(type(pattern)) # <class 're.Pattern'>\n",
    "# print(match) # <re.Match object; span=(39, 41), match='ma'>\n",
    "# 通过group方法获取Match对象匹配的内容\n",
    "print(match.group()) # ma"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d19115e-3687-4206-ba65-a95ec08ec13a",
   "metadata": {},
   "source": [
    "### 函数写法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "5b6bbb48-813f-4720-9c03-2fb4b775d866",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ma\n"
     ]
    }
   ],
   "source": [
    "my_str = 'Math is intresting, and I like learing mathmatics.'\n",
    "\n",
    "# 直接使用字符串作为匹配模式\n",
    "pattern = 'ma'\n",
    "\n",
    "match = re.search(pattern, my_str)\n",
    "# print(match) # <re.Match object; span=(39, 41), match='ma'>\n",
    "print(match.group()) # ma"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f584d1b-cc1a-49eb-9260-46380f3856f4",
   "metadata": {},
   "source": [
    "### findall"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "2a72e93b-4221-496a-8431-4a555616ff9f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Math is intresting, and I like learing mathmatics.', ['ma', 'ma'])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_str = 'Math is intresting, and I like learing mathmatics.'\n",
    "pattern = 'ma'\n",
    "matches = re.findall(pattern, my_str)\n",
    "my_str, matches"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "6581707c-ba33-4ffd-a8a6-46c8c9bd32aa",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('123', '456-7890')]\n"
     ]
    }
   ],
   "source": [
    "# findall处理有分组的情形\n",
    "my_str = 'My phone num is 123-456-7890.'\n",
    "pattern = re.compile(r'(\\d\\d\\d)-(\\d\\d\\d-\\d\\d\\d\\d)')\n",
    "match = pattern.findall(my_str)\n",
    "print(match) # [('123', '456-7890')]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee588c65-f40b-44da-97e9-32017f210499",
   "metadata": {},
   "source": [
    "### sub"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "09de158f-584a-4aad-8928-916feb69dbda",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hell0 W0rld!\n"
     ]
    }
   ],
   "source": [
    "my_str = 'Hello World!'\n",
    "pattern = 'o'\n",
    "new_str = re.sub(pattern, '0', my_str)\n",
    "print(new_str)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "915b05c7-839c-47e4-932d-e16725d63c9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "My email is d****c@foxmail.com, his email is t****m@example.com\n"
     ]
    }
   ],
   "source": [
    "my_str = 'My email is d111kc@foxmail.com, his email is tom@example.com'\n",
    "pattern = r'(\\w)\\w*(\\w)@'\n",
    "new_str = re.sub(pattern, r'\\1****\\2@', my_str)\n",
    "print(new_str)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52740c39-8488-47d2-a3c2-761eaf06cc87",
   "metadata": {},
   "source": [
    "### search"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc34f1be-f3ff-41fd-899d-93299953c2d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "my_str = 'My phone num is 123-456-7890.'\n",
    "pattern = re.compile(r'(\\d\\d\\d)-(\\d\\d\\d-\\d\\d\\d\\d)')\n",
    "match = pattern.search(my_str)\n",
    "# 无视分组\n",
    "print(match.group()) # 123-456-7890\n",
    "print(match.group(0)) # 123-456-7890\n",
    "# 第一组\n",
    "print(match.group(1)) # 123\n",
    "# 第二组\n",
    "print(match.group(2)) # 456-7890\n",
    "# 所有组的元组\n",
    "print(match.groups()) # ('123', '456-7890')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ef9f9ac-84f2-4d6a-95c4-ca4e3325272b",
   "metadata": {},
   "source": [
    "## 特殊字符"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e823f1a7-987a-4ee4-818b-4af389e49395",
   "metadata": {},
   "source": [
    "### `*` 匹配前面的模式 0次及以上"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "60f81ddb-db15-4d91-a35c-7cc8449ea758",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Batman\n",
      "Batwoman\n",
      "Batwowowowoman\n"
     ]
    }
   ],
   "source": [
    "pattern = re.compile(r'Bat(wo)*man')\n",
    "match1 = pattern.search('the adventure of Batman')\n",
    "print(match1.group()) # Batman\n",
    "match2 = pattern.search('the adventure of Batwoman')\n",
    "print(match2.group()) # Batwoman\n",
    "match3 = pattern.search('the adventure of Batwowowowoman')\n",
    "print(match3.group()) # Batwowowowoman"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "424a2269-3fd6-4b6b-91dc-d6ae3302b3ba",
   "metadata": {},
   "source": [
    "### `+` 匹配前面的模式 1次及以上"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "0099764f-eb0b-4575-a885-30fb2b516b50",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Batwoman\n",
      "Batwowowowoman\n"
     ]
    }
   ],
   "source": [
    "pattern = re.compile(r'Bat(wo)+man')\n",
    "match2 = pattern.search('the adventure of Batwoman')\n",
    "print(match2.group()) # Batwoman\n",
    "match3 = pattern.search('the adventure of Batwowowowoman')\n",
    "print(match3.group()) # Batwowowowoman"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff8a2205-0842-4d89-938e-c785994971d7",
   "metadata": {},
   "source": [
    "### `?` 匹配前面的模式 0或1次"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "f6d808c3-0674-4b97-999e-98cba9ec50a8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Batman\n",
      "Batwoman\n"
     ]
    }
   ],
   "source": [
    "pattern = re.compile(r'Bat(wo)?man')\n",
    "match1 = pattern.search('the adventure of Batman')\n",
    "print(match1.group()) # Batman\n",
    "match2 = pattern.search('the adventure of Batwoman')\n",
    "print(match2.group()) # Batwoman"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ce6fdd9-876f-41b1-a2be-a58f197d2ce7",
   "metadata": {},
   "source": [
    "### `?` 非贪心\n",
    "\n",
    "python正则表达式默认是贪心匹配"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "c989f29c-df41-4221-89ee-4d42a7e3cef9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HaHaHaHaHa\n",
      "HaHaHa\n"
     ]
    }
   ],
   "source": [
    "# 贪心\n",
    "greed_pattern = re.compile(r'(Ha){3,5}')\n",
    "mo1 = greed_pattern.search('HaHaHaHaHa')\n",
    "print(mo1.group()) # HaHaHaHaHa\n",
    "# 非贪心\n",
    "nongreed_pattern = re.compile(r'(Ha){3,5}?')\n",
    "mo2 = nongreed_pattern.search('HaHaHaHaHa')\n",
    "print(mo2.group()) # HaHaHa"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f511b8f-2289-43db-b999-815b04ddb44e",
   "metadata": {},
   "source": [
    "### `|` 或\n",
    "\n",
    "如果 `|` 左侧的符合匹配，那么右侧的失效。只有左侧不符合匹配，右侧才有效。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "6a21a2bf-4b50-4676-9f22-456ada290a86",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tom\n",
      "Jerry\n"
     ]
    }
   ],
   "source": [
    "pattern = re.compile('Tom|Jerry') # 匹配Tom或Jerry字符串\n",
    "match1 = pattern.search('Tom and Jerry')\n",
    "print(match1.group()) # Tom\n",
    "match2 = pattern.search('Jerry and Tom')\n",
    "print(match2.group()) # Jerry"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "0aeb34a3-fe5f-42bb-86c6-e362a3281fa4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello\n",
      "llo\n"
     ]
    }
   ],
   "source": [
    "pattern = re.compile(r'He(y|llo|ro)')\n",
    "match = pattern.search('Hello world.')\n",
    "print(match.group()) # Hello （无视分组）\n",
    "print(match.group(1)) # llo （第1组）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "776cc6a6-2c38-4fe7-af30-75acf3ae6974",
   "metadata": {},
   "source": [
    "### `[]` `[^]` 字符集\n",
    "\n",
    "在方括号 `[]` 内,普通的正则表达式符号不会被解释.你不需要前面加上倒斜杠转义`.`、`*`、`?`或`()`字符.例如,字符分类将匹配数字0到5和一个句点,则需要将它写成[0-5.] .通过在字符分类的左方括号后加上一个插入字符 `^`，可以取反"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68d019ca-e90f-46fe-b0c6-cf3cfceca003",
   "metadata": {},
   "source": [
    "### `.` 通配符，匹配除了换行符之外的任意单个字符"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "56d0522e-68ec-4532-9527-3eb05a4a3614",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['cat', 'hat', 'sat', 'lat', 'mat']\n"
     ]
    }
   ],
   "source": [
    "pattern = r'.at'\n",
    "my_str = 'The cat in the hat sat on the flat mat.'\n",
    "my_list = re.findall(pattern, my_str)\n",
    "print(my_list) # ['cat', 'hat', 'sat', 'lat', 'mat']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f48582fd-bc16-4032-b52e-204e04c8b3fd",
   "metadata": {},
   "source": [
    "### `.*` `.*?` 匹配任意文本\n",
    "\n",
    "`.*` 是贪心模式，`.*?` 是非贪心模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "581465b2-e6e4-4a48-ac70-0cc702d14d08",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hello world\n",
      "Hello\n"
     ]
    }
   ],
   "source": [
    "my_str = 'Hello world!'\n",
    "# 贪心\n",
    "pattern = re.compile(r'(.*) world')\n",
    "match = pattern.search(my_str)\n",
    "print(match.group()) # Hello world!\n",
    "print(match.group(1)) # Hello"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "26201b69-f733-4c4c-bd80-6498b66eb7e2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<Hello>\n",
      "<Hello> <World>\n"
     ]
    }
   ],
   "source": [
    "my_str = '<Hello> <World>'\n",
    "# 非贪心匹配\n",
    "nongreddy_pattern = re.compile(r'<.*?>')\n",
    "match1 = nongreddy_pattern.search(my_str)\n",
    "print(match1.group()) # 输出结果 <Hello>\n",
    "# 贪心匹配\n",
    "greedy_regex = re.compile(r'<.*>')\n",
    "match2 = greedy_regex.search(my_str)\n",
    "print(match2.group()) # 输出结果 <Hello> <World>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05dcf874-4295-4228-aed6-8f6e9fa403f5",
   "metadata": {},
   "source": [
    "## 应用举例\n",
    "\n",
    "### 拆分字符串"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "93c1db4a-da1f-46b4-bda2-25b1ec40d7c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "窗前明月光\n",
      "疑是地上霜\n",
      "举头望明月\n",
      "低头思故乡\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "\n",
    "poem = '窗前明月光，疑是地上霜。举头望明月，低头思故乡。'\n",
    "sentences_list = re.split(r'[，。]', poem)\n",
    "sentences_list = [sentence for sentence in sentences_list if sentence]\n",
    "for sentence in sentences_list:\n",
    "    print(sentence)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "160a0539-d3fe-476d-8107-d245ab55031f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
